# -*- coding: utf-8 -*-
'''
Created on 2013-9-12

@author: johnw
Original reason to enhance the translation:
when user click translation icon of the field with translate=True, 
OpenERP has issues in the opened ir_translation window:
1.If add one new language, for sample, add one 'en_US' language, 
  OpenERP can not set the ir_translation.res_id, so the system can not use the new item added by user
2.If change the 'src', and OpenERP can not use the new src as the English value

so we want to change code to follow below targets:
1.In the opened translation widow, user can change the src as english text, and the value as the zh_CN text, 
  and when user returned the model GUI, the english and chinese value can be refreshed
2.In the model view GUI, including both english and chinese GUI,if user changed the name value,  
  then the ir_translation data will be updated, src store the english, and value store the chinese

chang log:
1.ir_translation._get_ids()
  when getting the english, use the src as the value when the english translation not exists
2.ir_translation._set_ids()  
    if the language is english, 
    then insert one zh_CN record with both src and value is src when transaltion does not exists
    or update src when record exists
3.ir_translation.write()
 when do writting, if the src changed, then clear the en_US translation cache to make sure the GUI synchroized 
4.openerp.osv.orm.BaseModel.write()
    also do translation saving under en_US language
    old code: totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
    ==> 
    new code: totranslate = context.get('lang', False)
         
'''
import openerp
from openerp.osv import fields,osv
from openerp import tools
class ir_translation(osv.osv):
    _inherit = 'ir.translation'
    @tools.ormcache_multi(skiparg=3, multi=6)
    def _get_ids(self, cr, uid, name, tt, lang, ids):
#        if name == 'res.pay.account,tt_person':
#            print 'break';
        translations = dict.fromkeys(ids, False)
        if ids:
            cr.execute('select res_id,value '
                    'from ir_translation '
                    'where lang=%s '
                        'and type=%s '
                        'and name=%s '
                        'and res_id IN %s',
                    (lang,tt,name,tuple(ids)))
            for res_id, value in cr.fetchall():
                translations[res_id] = value
            #johnw@20130911, when getting the english, use the src as the value when the english translation not exists
            if lang == 'en_US':
                missing_ids = []
                for id in ids:
                    if translations[id] == False:
                        missing_ids.append(id)
                if len(missing_ids) > 0:
                    cr.execute('select res_id,src '
                            'from ir_translation '
                            'where type=%s '
                                'and name=%s '
                                'and res_id IN %s limit 1',
                            (tt,name,tuple(missing_ids)))
                    for res_id, value in cr.fetchall():
                        translations[res_id] = value                
        return translations
    def _set_ids(self, cr, uid, name, tt, lang, ids, value, src=None):
        # clear the caches
        tr = self._get_ids(cr, uid, name, tt, lang, ids)
        for res_id in tr:
            if tr[res_id]:
                self._get_source.clear_cache(self, uid, name, tt, lang, tr[res_id])
            self._get_ids.clear_cache(self, uid, name, tt, lang, res_id)
        self._get_source.clear_cache(self, uid, name, tt, lang)
        #deal the 'en_US' ************begin**************
        main_lang = 'en_US'
        if lang == main_lang:
            #johnw@20130911, 
            #if the language is english, 
            #then insert one zh_CN record with both src and value is src when transaltion does not exists
            #or update src when record exists
            lg = 'zh_CN'
            src = value
            # Check if record exists, else create it (at once)
            sql = """INSERT INTO ir_translation (lang, src, name, type, res_id, value)
                SELECT %s, %s, %s, 'model', %s, %s WHERE NOT EXISTS
                (SELECT 1 FROM ir_translation WHERE lang=%s AND name=%s AND res_id=%s AND type='model');
                UPDATE ir_translation SET src = %s WHERE lang=%s AND name=%s AND res_id=%s AND type='model';
                """
            for id in ids:
                cr.execute(sql, (lg, src , name, id, src, lg, name, id, src, lg, name, id))
            return
        #deal the 'en_US' ************end**************
        cr.execute('delete from ir_translation '
                'where lang=%s '
                    'and type=%s '
                    'and name=%s '
                    'and res_id IN %s',
                (lang,tt,name,tuple(ids),))
        for id in ids:
            self.create(cr, uid, {
                'lang':lang,
                'type':tt,
                'name':name,
                'res_id':id,
                'value':value,
                'src':src,
                })
        return len(ids)    
    #johnw@20130911, when do writting, if the src changed, then clear the en_US translation cache to make sure the GUI synchroized
    def write(self, cursor, user, ids, vals, context=None):
        result = super(ir_translation,self).write(cursor, user, ids, vals, context)
        if vals.get('src'):
            for trans_obj in self.read(cursor, user, ids, ['name','type','res_id','src'], context=context):
                    self._get_source.clear_cache(self, user, trans_obj['name'], trans_obj['type'], 'en_US', trans_obj['src'])
                    self._get_ids.clear_cache(self, user, trans_obj['name'], trans_obj['type'], 'en_US', trans_obj['res_id'])                
        return result       
    
import babel.dates
import calendar
import collections
import copy
import datetime
import itertools
import logging
import operator
import pickle
import re
import simplejson
import time
import traceback
import types

import psycopg2
from lxml import etree

import openerp.osv.fields
import openerp
import openerp.netsvc as netsvc
import openerp.tools as tools
from openerp.tools.config import config
from openerp.tools.misc import CountingStream
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
from openerp import SUPERUSER_ID

_logger = logging.getLogger(__name__)
from openerp.osv.orm import except_orm
#
# TODO: Validate
#
def write(self, cr, user, ids, vals, context=None):
    """
    Update records with given ids with the given field values

    :param cr: database cursor
    :param user: current user id
    :type user: integer
    :param ids: object id or list of object ids to update according to **vals**
    :param vals: field values to update, e.g {'field_name': new_field_value, ...}
    :type vals: dictionary
    :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
    :type context: dictionary
    :return: True
    :raise AccessError: * if user has no write rights on the requested object
                        * if user tries to bypass access rules for write on the requested object
    :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
    :raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)

    **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:

        + For a many2many field, a list of tuples is expected.
          Here is the list of tuple that are accepted, with the corresponding semantics ::

             (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
             (1, ID, { values })    update the linked record with id = ID (write *values* on it)
             (2, ID)                remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
             (3, ID)                cut the link to the linked record with id = ID (delete the relationship between the two objects but does not delete the target object itself)
             (4, ID)                link to existing record with id = ID (adds a relationship)
             (5)                    unlink all (like using (3,ID) for all linked records)
             (6, 0, [IDs])          replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)

             Example:
                [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]

        + For a one2many field, a lits of tuples is expected.
          Here is the list of tuple that are accepted, with the corresponding semantics ::

             (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
             (1, ID, { values })    update the linked record with id = ID (write *values* on it)
             (2, ID)                remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)

             Example:
                [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]

        + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
        + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)

    """
    readonly = None
    self.check_field_access_rights(cr, user, 'write', vals.keys())
    for field in vals.copy():
        fobj = None
        if field in self._columns:
            fobj = self._columns[field]
        elif field in self._inherit_fields:
            fobj = self._inherit_fields[field][2]
        if not fobj:
            continue
        groups = fobj.write

        if groups:
            edit = False
            for group in groups:
                module = group.split(".")[0]
                grp = group.split(".")[1]
                cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name=%s and module=%s and model=%s) and uid=%s", \
                           (grp, module, 'res.groups', user))
                readonly = cr.fetchall()
                if readonly[0][0] >= 1:
                    edit = True
                    break

            if not edit:
                vals.pop(field)

    if not context:
        context = {}
    if not ids:
        return True
    if isinstance(ids, (int, long)):
        ids = [ids]

    self._check_concurrency(cr, ids, context)
    self.check_access_rights(cr, user, 'write')

    result = self._store_get_values(cr, user, ids, vals.keys(), context) or []

    # No direct update of parent_left/right
    vals.pop('parent_left', None)
    vals.pop('parent_right', None)

    parents_changed = []
    parent_order = self._parent_order or self._order
    if self._parent_store and (self._parent_name in vals):
        # The parent_left/right computation may take up to
        # 5 seconds. No need to recompute the values if the
        # parent is the same.
        # Note: to respect parent_order, nodes must be processed in
        # order, so ``parents_changed`` must be ordered properly.
        parent_val = vals[self._parent_name]
        if parent_val:
            query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL) ORDER BY %s" % \
                            (self._table, self._parent_name, self._parent_name, parent_order)
            cr.execute(query, (tuple(ids), parent_val))
        else:
            query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \
                            (self._table, self._parent_name, parent_order)
            cr.execute(query, (tuple(ids),))
        parents_changed = map(operator.itemgetter(0), cr.fetchall())

    upd0 = []
    upd1 = []
    upd_todo = []
    updend = []
    direct = []
#        totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
    #@johnw@20130911, also do translation saving under en_US language
    totranslate = context.get('lang', False)
    for field in vals:
        field_column = self._all_columns.get(field) and self._all_columns.get(field).column
        if field_column and field_column.deprecated:
            _logger.warning('Field %s.%s is deprecated: %s', self._name, field, field_column.deprecated)
        if field in self._columns:
            if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
                if (not totranslate) or not self._columns[field].translate:
                    upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
                    upd1.append(self._columns[field]._symbol_set[1](vals[field]))
                direct.append(field)
            else:
                upd_todo.append(field)
        else:
            updend.append(field)
        if field in self._columns \
                and hasattr(self._columns[field], 'selection') \
                and vals[field]:
            self._check_selection_field_value(cr, user, field, vals[field], context=context)

    if self._log_access:
        upd0.append('write_uid=%s')
        upd0.append("write_date=(now() at time zone 'UTC')")
        upd1.append(user)

    if len(upd0):
        self.check_access_rule(cr, user, ids, 'write', context=context)
        for sub_ids in cr.split_for_in_conditions(ids):
            cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
                       'where id IN %s', upd1 + [sub_ids])
            if cr.rowcount != len(sub_ids):
                raise except_orm(_('AccessError'),
                                 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)

        if totranslate:
            # TODO: optimize
            for f in direct:
                if self._columns[f].translate:
                    src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
                    if not src_trans:
                        src_trans = vals[f]
                        # Inserting value to DB
                        self.write(cr, user, ids, {f: vals[f]})
                    self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)


    # call the 'set' method of fields which are not classic_write
    upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)

    # default element in context must be removed when call a one2many or many2many
    rel_context = context.copy()
    for c in context.items():
        if c[0].startswith('default_'):
            del rel_context[c[0]]

    for field in upd_todo:
        for id in ids:
            result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []

    unknown_fields = updend[:]
    for table in self._inherits:
        col = self._inherits[table]
        nids = []
        for sub_ids in cr.split_for_in_conditions(ids):
            cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
                       'where id IN %s', (sub_ids,))
            nids.extend([x[0] for x in cr.fetchall()])

        v = {}
        for val in updend:
            if self._inherit_fields[val][0] == table:
                v[val] = vals[val]
                unknown_fields.remove(val)
        if v:
            self.pool.get(table).write(cr, user, nids, v, context)

    if unknown_fields:
        _logger.warning(
            'No such field(s) in model %s: %s.',
            self._name, ', '.join(unknown_fields))
    self._validate(cr, user, ids, context)

    # TODO: use _order to set dest at the right position and not first node of parent
    # We can't defer parent_store computation because the stored function
    # fields that are computer may refer (directly or indirectly) to
    # parent_left/right (via a child_of domain)
    if parents_changed:
        if self.pool._init:
            self.pool._init_parent[self._name] = True
        else:
            order = self._parent_order or self._order
            parent_val = vals[self._parent_name]
            if parent_val:
                clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
            else:
                clause, params = '%s IS NULL' % (self._parent_name,), ()

            for id in parents_changed:
                cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
                pleft, pright = cr.fetchone()
                distance = pright - pleft + 1

                # Positions of current siblings, to locate proper insertion point;
                # this can _not_ be fetched outside the loop, as it needs to be refreshed
                # after each update, in case several nodes are sequentially inserted one
                # next to the other (i.e computed incrementally)
                cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, parent_order), params)
                parents = cr.fetchall()

                # Find Position of the element
                position = None
                for (parent_pright, parent_id) in parents:
                    if parent_id == id:
                        break
                    position = parent_pright + 1

                # It's the first node of the parent
                if not position:
                    if not parent_val:
                        position = 1
                    else:
                        cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
                        position = cr.fetchone()[0] + 1

                if pleft < position <= pright:
                    raise except_orm(_('UserError'), _('Recursivity Detected.'))

                if pleft < position:
                    cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
                    cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
                    cr.execute('update '+self._table+' set parent_left=parent_left+%s, parent_right=parent_right+%s where parent_left>=%s and parent_left<%s', (position-pleft, position-pleft, pleft, pright))
                else:
                    cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
                    cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
                    cr.execute('update '+self._table+' set parent_left=parent_left-%s, parent_right=parent_right-%s where parent_left>=%s and parent_left<%s', (pleft-position+distance, pleft-position+distance, pleft+distance, pright+distance))

    result += self._store_get_values(cr, user, ids, vals.keys(), context)
    result.sort()

    done = {}
    for order, object, ids_to_update, fields_to_recompute in result:
        key = (object, tuple(fields_to_recompute))
        done.setdefault(key, {})
        # avoid to do several times the same computation
        todo = []
        for id in ids_to_update:
            if id not in done[key]:
                done[key][id] = True
                todo.append(id)
        self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)

    self._workflow_trigger(cr, user, ids, 'trg_write', context=context)
    return True     

openerp.osv.orm.BaseModel.write = write